package util

import (
	"context"
	"fmt"
	"math/big"
	"strings"
	"time"

	"github.com/filecoin-project/lotus/build"
	"github.com/ipfs/go-cid"
	ma "github.com/multiformats/go-multiaddr"
	dns "github.com/multiformats/go-multiaddr-dns"
)

var (
	// AvgBlockTime is the expected duration between block in two consecutive epochs.
	AvgBlockTime = time.Second * time.Duration(EpochDurationSeconds)
)

const (
	// EpochDurationSeconds is the expected duration in seconds of an epoch.
	// Defined at the filecoin spec level.
	EpochDurationSeconds = 30

	// MinDealDuration is the minium deal duration accepted in the Filecoin network.
	// Original calculation: 180 * EpochsInADay.
	MinDealDuration = 180 * (24 * 60 * 60 / EpochDurationSeconds)

	// CidUndef is a magic value to represent an undefined cid as a string.
	CidUndef = "CID_UNDEF"

	// DefaultCidUndef is the string generated by the cid module's String() func for an undefined cid.
	DefaultCidUndef = "b"
)

// TCPAddrFromMultiAddr converts a multiaddress to a string representation of a tcp address.
func TCPAddrFromMultiAddr(maddr ma.Multiaddr) (string, error) {
	if maddr == nil {
		return "", fmt.Errorf("invalid address")
	}

	var ip string
	if _, err := maddr.ValueForProtocol(ma.P_DNS4); err == nil {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		maddrs, err := dns.Resolve(ctx, maddr)
		if err != nil {
			return "", fmt.Errorf("resolving dns: %s", err)
		}
		for _, m := range maddrs {
			if ip, err = getIPFromMaddr(m); err == nil {
				break
			}
		}
	} else {
		ip, err = getIPFromMaddr(maddr)
		if err != nil {
			return "", fmt.Errorf("getting ip from maddr: %s", err)
		}
	}

	tcp, err := maddr.ValueForProtocol(ma.P_TCP)
	if err != nil {
		return "", fmt.Errorf("getting port from maddr: %s", err)
	}
	return fmt.Sprintf("%s:%s", ip, tcp), nil
}

func getIPFromMaddr(maddr ma.Multiaddr) (string, error) {
	if ip, err := maddr.ValueForProtocol(ma.P_IP4); err == nil {
		return ip, nil
	}
	if ip, err := maddr.ValueForProtocol(ma.P_IP6); err == nil {
		return fmt.Sprintf("[%s]", ip), nil
	}
	return "", fmt.Errorf("no ip in multiaddr")
}

// MustParseAddr returns a parsed Multiaddr, or panics if invalid.
func MustParseAddr(str string) ma.Multiaddr {
	addr, err := ma.NewMultiaddr(str)
	if err != nil {
		panic(err)
	}
	return addr
}

// CidToString converts a cid to string, representing cid.Undef as an empty string.
func CidToString(c cid.Cid) string {
	if c == cid.Undef {
		return CidUndef
	}
	return c.String()
}

// CidFromString converts a string to a cid assuming that an empty string is cid.Undef.
func CidFromString(c string) (cid.Cid, error) {
	if c == DefaultCidUndef || c == CidUndef || c == "" {
		return cid.Undef, nil
	}
	return cid.Decode(c)
}

// AttoFilToFil transforms an attoFIL integer value, to a
// pretty FIL string.
func AttoFilToFil(attoFil uint64) string {
	r := new(big.Rat).SetFrac(big.NewInt(int64(attoFil)), big.NewInt(int64(build.FilecoinPrecision)))
	if r.Sign() == 0 {
		return "0"
	}
	return strings.TrimRight(strings.TrimRight(r.FloatString(18), "0"), ".")
}
